/*

   Derby - Class org.apache.derby.impl.jdbc.EmbedPreparedStatement

   Copyright 1997, 2004 The Apache Software Foundation or its licensors, as applicable.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.impl.jdbc;

import org.apache.derby.iapi.services.sanity.SanityManager;

import org.apache.derby.iapi.types.VariableSizeDataValue;

import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.sql.PreparedStatement;
import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;
import org.apache.derby.iapi.sql.ResultSet;
import org.apache.derby.iapi.sql.Activation;
import org.apache.derby.iapi.sql.ParameterValueSet;
import org.apache.derby.iapi.sql.ResultDescription;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.DataValueDescriptor;

import org.apache.derby.iapi.error.StandardException;

import org.apache.derby.iapi.services.io.LimitReader;

import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.reference.JDBC30Translation;
import org.apache.derby.iapi.reference.JDBC20Translation;

import java.util.Calendar;
import java.util.Vector;

/*
 We would import these, but have name-overlap
import java.sql.PreparedStatement;
import java.sql.ResultSet;
*/
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Clob;
import java.sql.Blob;

import java.math.BigDecimal;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.EOFException;
import java.io.Reader;
import java.sql.Types;


/**
 *
 * EmbedPreparedStatement is a local JDBC statement.
 *
 */
public abstract class EmbedPreparedStatement
	extends EmbedStatement
	implements java.sql.PreparedStatement
{

	//Moving jdbc2.0 batch related code in this class because callableStatement in jdbc 20 needs
	//this code too and it doesn't derive from prepared statement in jdbc 20 in our implementation.

	protected ResultSetMetaData rMetaData;
	//bug 4579-If the prepared statement was revalidated after doing getMetaData(), we
	//should get the metadata info again on next getMetaData(). We store the generated
	//class name in following variable during getMetaData() call. If it differs from the
	//current generated class name, then that indicates a refetch of metadata is required.
	private String			gcDuringGetMetaData;

	protected PreparedStatement	preparedStatement;
	private Activation			activation;

	/*
		Constructor assumes caller will setup context stack
		and restore it.
	 */
	public EmbedPreparedStatement (EmbedConnection conn, String sql, boolean forMetaData,
									  int resultSetType, int resultSetConcurrency,
									  int resultSetHoldability,
									  int autoGeneratedKeys,
									  int[] columnIndexes,
									  String[] columnNames)
		throws SQLException {

		super(conn, forMetaData, resultSetType, resultSetConcurrency, resultSetHoldability);

		// if the sql string is null, raise an error
		if (sql == null)
  			throw newSQLException(SQLState.NULL_SQL_TEXT);

			// set up the SQLText in EmbedStatement
			SQLText = sql;

			try {
			    preparedStatement = lcc.prepareInternalStatement(sql);
				getWarnings(preparedStatement.getCompileTimeWarnings());

			    activation = preparedStatement.getActivation(lcc, resultSetType == JDBC20Translation.TYPE_SCROLL_INSENSITIVE);

				checkRequiresCallableStatement(activation);

			//bug 4838 - save the auto-generated key information in activation. keeping this
			//information in lcc will not work work as it can be tampered by a nested trasaction
  				if (autoGeneratedKeys == JDBC30Translation.RETURN_GENERATED_KEYS)
  					activation.setAutoGeneratedKeysResultsetInfo(columnIndexes, columnNames);

			} catch (Throwable t) {
		    throw handleException(t);
			}
	}

	/**
		JDBC states that a Statement is closed when garbage collected.

		@exception Throwable Allows any exception to be thrown during finalize
	*/
	protected void finalize() throws Throwable {
		super.finalize();

		/*
		** We mark the activation as not being used and
	 	** that is it.  We rely on the connection to sweep
		** through the activations to find the ones that
		** aren't in use, and to close them.  We cannot
	 	** do a activation.close() here because there are
		** synchronized methods under close that cannot
		** be called during finalization.
		*/
		if (activation != null) 
		{
			activation.markUnused();
		}
	}

	/*
	 * Statement interface
		we override all Statement methods that take a SQL
		string as they must thrown an exception in a PreparedStatement.
		See the JDBC 3.0 spec.
	 */
	public final boolean execute(String sql) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "execute(String)");
	}
	public final boolean execute(String sql, int autoGenKeys) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "execute(String, int)");
	}
	public final boolean execute(String sql, int[] columnIndexes) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "execute(String, int[])");
	}
	public final boolean execute(String sql, String[] columnNames) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "execute(String, String[])");
	}
	public final java.sql.ResultSet executeQuery(String sql) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "executeQuery(String)");
	}
	public final int executeUpdate(String sql) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "executeUpdate(String)");
	}
	public final int executeUpdate(String sql, int autoGenKeys) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "executeUpdate(String, int)");
	}
	public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "executeUpdate(String, int[])");
	}
	public final int executeUpdate(String sql, String[] columnNames) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "executeUpdate(String, String[])");
	}
	public final void addBatch(String sql) throws SQLException {
		throw newSQLException(SQLState.NOT_FOR_PREPARED_STATEMENT, "addBatch(String)");
	}


	/**
		Additional close to close our activation.

		@exception SQLException	thrown on failure
	 */
	protected void closeActions() throws SQLException {

		//we release the resource for preparedStatement
		preparedStatement = null;

		try{
			setupContextStack();
		} catch (SQLException se) {
			//we may have already committed the transaction in which case
			//setupContextStack will fail, the close should just return
			return;
		}
		try
		{
		    activation.close();
			activation = null;
		} catch (Throwable t)
		{
			throw handleException(t);
		} finally {
		    restoreContextStack();
		}
	}
	
	/*
	 * PreparedStatement interface; we have inherited from
	 * EmbedStatement to get the Statement interface for
	 * EmbedPreparedStatement (needed by PreparedStatement)
	 * These are the JDBC interface comments, so we know
	 * what to do.
	 */

	/**
     * A prepared SQL query is executed and its ResultSet is returned.
     *
     * @return a ResultSet that contains the data produced by the
     * query; never null
	 * @exception SQLException thrown on failure.
     */
	public final java.sql.ResultSet executeQuery() throws SQLException {
		executeStatement(activation, true, false);

		if (SanityManager.DEBUG) {
			if (results == null)
				SanityManager.THROWASSERT("no results returned on executeQuery()");
		}

		return results;
	}

    /**
     * Execute a SQL INSERT, UPDATE or DELETE statement. In addition,
     * SQL statements that return nothing such as SQL DDL statements
     * can be executed.
     *
     * @return either the row count for INSERT, UPDATE or DELETE; or 0
     * for SQL statements that return nothing
	 * @exception SQLException thrown on failure.
     */
	public final int executeUpdate() throws SQLException {
		executeStatement(activation, false, true);
		return updateCount;
	}

    /**
     * Set a parameter to SQL NULL.
     *
     * <P><B>Note:</B> You must specify the parameter's SQL type.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param sqlType SQL type code defined by java.sql.Types
	 * @exception SQLException thrown on failure.
     */
    public void setNull(int parameterIndex, int sqlType) throws SQLException {

		checkStatus();

		int jdbcTypeId = getParameterJDBCType(parameterIndex);
		
		if (!DataTypeDescriptor.isJDBCTypeEquivalent(jdbcTypeId, sqlType)) {

			throw dataTypeConversion(parameterIndex, Util.typeName(sqlType));
		}
		
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setToNull();
		} catch (StandardException t) {
			throw EmbedResultSet.noStateChangeException(t);
		}

	}

    /**
     * Set a parameter to a Java boolean value.  According to the JDBC API spec,
	 * the driver converts this to a SQL BIT value when it sends it to the
	 * database. But we don't have to do this, since the database engine
	 * supports a boolean type.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
		
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (StandardException t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a Java byte value.  The driver converts this
     * to a SQL TINYINT value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setByte(int parameterIndex, byte x) throws SQLException {

		checkStatus();
		try {

			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a Java short value.  The driver converts this
     * to a SQL SMALLINT value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setShort(int parameterIndex, short x) throws SQLException {

		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a Java int value.  The driver converts this
     * to a SQL INTEGER value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setInt(int parameterIndex, int x) throws SQLException {
		checkStatus();

		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);
		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a Java long value.  The driver converts this
     * to a SQL BIGINT value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setLong(int parameterIndex, long x) throws SQLException {
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}

	}

    /**
     * Set a parameter to a Java float value.  The driver converts this
     * to a SQL FLOAT value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setFloat(int parameterIndex, float x) throws SQLException {
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}

	}

    /**
     * Set a parameter to a Java double value.  The driver converts this
     * to a SQL DOUBLE value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setDouble(int parameterIndex, double x) throws SQLException {
		checkStatus();

		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}

	}

    /**
     * Set a parameter to a java.lang.BigDecimal value.  
     * The driver converts this to a SQL NUMERIC value when
     * it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}
    /**
     * Set a parameter to a Java String value.  The driver converts this
     * to a SQL VARCHAR or LONGVARCHAR value (depending on the arguments
     * size relative to the driver's limits on VARCHARs) when it sends
     * it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setString(int parameterIndex, String x) throws SQLException {
		checkStatus();		
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a Java array of bytes.  The driver converts
     * this to a SQL VARBINARY or LONGVARBINARY (depending on the
     * argument's size relative to the driver's limits on VARBINARYs)
     * when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value 
	 * @exception SQLException thrown on failure.
     */
    public void setBytes(int parameterIndex, byte x[]) throws SQLException {
		checkStatus();

		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}

	}

    /**
     * Set a parameter to a java.sql.Date value.  The driver converts this
     * to a SQL DATE value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setDate(int parameterIndex, Date x) throws SQLException {
        setDate( parameterIndex, x, (Calendar) null);
	}

    /**
     * Set a parameter to a java.sql.Time value.  The driver converts this
     * to a SQL TIME value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
	 * @exception SQLException thrown on failure.
     */
    public void setTime(int parameterIndex, Time x) throws SQLException {
        setTime( parameterIndex, x, (Calendar) null);
	}

    /**
     * Set a parameter to a java.sql.Timestamp value.  The driver
     * converts this to a SQL TIMESTAMP value when it sends it to the
     * database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value 
	 * @exception SQLException thrown on failure.
     */
    public void setTimestamp(int parameterIndex, Timestamp x)
	    throws SQLException {
        setTimestamp( parameterIndex, x, (Calendar) null);
	}

    /**
	 * We do this inefficiently and read it all in here. The target type
	 * is assumed to be a String.
     * 
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the java input stream which contains the ASCII parameter value
     * @param length the number of bytes in the stream 
	 * @exception SQLException thrown on failure.
     */
    public final void setAsciiStream(int parameterIndex, InputStream x, int length)
	    throws SQLException {
		checkStatus();

		int jdbcTypeId = getParameterJDBCType(parameterIndex);
		
		switch (jdbcTypeId) {
		case Types.CHAR:
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
		case Types.CLOB:
			break;
		default:
			throw dataTypeConversion(parameterIndex, "java.io.InputStream(ASCII)");
		}

		java.io.Reader r = null;

		if (x != null)
		{
			// Use ISO-8859-1 and not US-ASCII as JDBC seems to define
			// ASCII as 8 bits. US-ASCII is 7.
			try {
				r = new java.io.InputStreamReader(x, "ISO-8859-1");
			} catch (java.io.UnsupportedEncodingException uee) {
				throw new SQLException(uee.getMessage());
			}
		}

		setCharacterStream(parameterIndex, r, length);
	}

    /**
		Deprecated in JDBC 3.0
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the java input stream which contains the
     * UNICODE parameter value
     * @param length the number of bytes in the stream
	 * @exception SQLException thrown on failure.
     */
    public void setUnicodeStream(int parameterIndex, InputStream x, int length)
	    throws SQLException
	{
		throw Util.notImplemented("setUnicodeStream");
	}

    /**
     * JDBC 2.0
     *
     * When a very large UNICODE value is input to a LONGVARCHAR
     * parameter, it may be more practical to send it via a
     * java.io.Reader. JDBC will read the data from the stream
     * as needed, until it reaches end-of-file.  The JDBC driver will
     * do any necessary conversion from UNICODE to the database char format.
     *
     * <P><B>Note:</B> This stream object can either be a standard
     * Java stream object or your own subclass that implements the
     * standard interface.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the java reader which contains the UNICODE data
     * @param length the number of characters in the stream
     * @exception SQLException if a database-access error occurs.
     */
    public final void setCharacterStream(int parameterIndex,
       			  java.io.Reader reader,
			  int length) throws SQLException
	{
		int jdbcTypeId = getParameterJDBCType(parameterIndex);
		switch (jdbcTypeId) {
		case Types.CHAR:
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
		case Types.CLOB:
			break;
		default:
			throw dataTypeConversion(parameterIndex, "java.io.Reader");
		}
		if (length < 0) //we are doing the check here and not in setCharacterStreamInternal becuase setClob needs to pass -1 for length.
			throw newSQLException(SQLState.NEGATIVE_STREAM_LENGTH);

		if (reader == null)
		{
			setNull(parameterIndex, jdbcTypeId);
			return;
		}

		setCharacterStreamInternal(parameterIndex, reader, length);
	}

    protected void setCharacterStreamInternal(int parameterIndex,
						Reader reader, int length)
	    throws SQLException
	{
		checkStatus();

		int jdbcTypeId = getParameterJDBCType(parameterIndex);


		try {
			ParameterValueSet pvs = getParms();

			LimitReader limitIn = new LimitReader(reader);
			if (length != -1)
				limitIn.setLimit(length);
			ReaderToUTF8Stream utfIn = new ReaderToUTF8Stream(limitIn);

			/* JDBC is one-based, DBMS is zero-based */
			pvs.getParameterForSet(parameterIndex - 1).setValue(utfIn, length);

		} catch (StandardException t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * 
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the java input stream which contains the binary parameter value
     * @param length the number of bytes in the stream 
	 * @exception SQLException thrown on failure.
     */
    public final void setBinaryStream(int parameterIndex, InputStream x, int length)
	    throws SQLException
		{

		int jdbcTypeId = getParameterJDBCType(parameterIndex);
		switch (jdbcTypeId) {
		case Types.BINARY:
		case Types.VARBINARY:
		case Types.LONGVARBINARY:
		case Types.BLOB:
			break;
		default:
			throw dataTypeConversion(parameterIndex, "java.io.InputStream");
		}
		if (length < 0) //we are doing the check here and not in setBinaryStreamInternal becuase setBlob needs to pass -1 for length.
			throw newSQLException(SQLState.NEGATIVE_STREAM_LENGTH);

    	setBinaryStreamInternal(parameterIndex, x, length);
	}

    protected void setBinaryStreamInternal(int parameterIndex, InputStream x,
				int length)
	    throws SQLException
	{
		checkStatus();
		int jdbcTypeId = getParameterJDBCType(parameterIndex);
		if (x == null) {
			setNull(parameterIndex, jdbcTypeId);
           	return;
		}

		try {

			getParms().getParameterForSet(parameterIndex - 1).setValue(new RawToBinaryFormatStream(x, length), length);

		} catch (StandardException t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

	/////////////////////////////////////////////////////////////////////////
	//
	//	JDBC 2.0	-	New public methods
	//
	/////////////////////////////////////////////////////////////////////////

    /**
     * JDBC 2.0
     *
     * Set null for user-named types and REF type parameters
     * 
     * @exception SQLException if a database-access error occurs.
     */
	public void setNull(int paramIndex,
						int sqlType,
						String typeName)
		 throws SQLException
	{
		throw Util.notImplemented("setNull");
	}

    /**
     * JDBC 2.0
     *
     * Add a set of parameters to the batch.
     * 
     * @exception SQLException if a database-access error occurs.
     */
    public void addBatch() throws SQLException {
	  checkStatus();

	  // need to synchronized to ensure that two threads
	  // don't both create a Vector at the same time. This
	  // would lead to one of the set of parameters being thrown
	  // away
  	  synchronized (getConnectionSynchronization()) {
  			if (batchStatements == null)
  				batchStatements = new Vector();

          //get a clone of the parameterValueSet and save it in the vector
          //which will be used later on at the time of batch execution.
          //This way we will get a copy of the current statement's parameter
          //values rather than a pointer to the statement's parameter value
          //set which will change with every new statement in the batch.
          batchStatements.addElement(getParms().getClone());
          clearParameters();
  	  }
    }

	protected boolean executeBatchElement(Object batchElement) throws SQLException, StandardException {
		
		ParameterValueSet temp = (ParameterValueSet) batchElement;

		int numberOfParameters = temp.getParameterCount();

		for (int j=0; j<numberOfParameters; j++) {
			temp.getParameter(j).setInto(this, j + 1);
		}

		return super.executeStatement(activation, false, true);
	}

 

    /**
     * <P>In general, parameter values remain in force for repeated use of a
     * Statement. Setting a parameter value automatically clears its
     * previous value.  However, in some cases it is useful to immediately
     * release the resources used by the current parameter values; this can
     * be done by calling clearParameters.
	 * @exception SQLException thrown on failure.
     */
    public void clearParameters() throws SQLException {
		checkStatus();

		ParameterValueSet pvs = getParms();
		if (pvs != null)
			pvs.clearParameters();
	}

    /**
	 * JDBC 2.0
	 *
     * The number, types and properties of a ResultSet's columns
     * are provided by the getMetaData method.
     *
     * @return the description of a ResultSet's columns
     * @exception SQLException Feature not implemented for now.
     */
    public java.sql.ResultSetMetaData getMetaData() throws SQLException
	{
		checkExecStatus();
		synchronized (getConnectionSynchronization())
		{
			//reason for casting is getActivationClass is not available on PreparedStatement
			ExecPreparedStatement execp = (ExecPreparedStatement)preparedStatement;

			setupContextStack(); // make sure there's context

			try {
				//bug 4579 - if the statement is invalid, regenerate the metadata info
				if (preparedStatement.isValid() == false)
				{
					//need to revalidate the statement here, otherwise getResultDescription would
					//still have info from previous valid statement
					preparedStatement.rePrepare(lcc);
					rMetaData = null;
				}
				//bug 4579 - gcDuringGetMetaData will be null if this is the first time
				//getMetaData call is made.
				//Second check - if the statement was revalidated since last getMetaData call,
				//then gcDuringGetMetaData wouldn't match with current generated class name
				if (gcDuringGetMetaData == null || gcDuringGetMetaData.equals(execp.getActivationClass().getName()) == false)
				{
					rMetaData = null;
					gcDuringGetMetaData = execp.getActivationClass().getName();
				}
				if (rMetaData == null)
				{
					ResultDescription resd = preparedStatement.getResultDescription();
					if (resd != null)
					{
						// Internally, the result description has information
						// which is used for insert, update and delete statements
						// Externally, we decided that statements which don't
						// produce result sets such as insert, update and delete
						// should not return ResultSetMetaData.  This is enforced
						// here
						String statementType = resd.getStatementType();
						if (statementType.equals("INSERT") ||
								statementType.equals("UPDATE") ||
								statementType.equals("DELETE"))
							rMetaData = null;
						else
				    		rMetaData = newEmbedResultSetMetaData(resd);
					}
				}
			} catch (Throwable t) {
				throw handleException(t);
			}	finally {
				restoreContextStack();
			}
		}
		return rMetaData;
	}

    //----------------------------------------------------------------------
    // Advanced features:

    /**
	 * The interface says that the type of the Object parameter must
	 * be compatible with the type of the targetSqlType. We check that,
	 * and if it flies, we expect the underlying engine to do the
	 * required conversion once we pass in the value using its type.
	 * So, an Integer converting to a CHAR is done via setInteger()
	 * support on the underlying CHAR type.
     *
     * <p>If x is null, it won't tell us its type, so we pass it on to setNull
     *
     * @param parameterIndex The first parameter is 1, the second is 2, ...
     * @param x The object containing the input parameter value
     * @param targetSqlType The SQL type (as defined in java.sql.Types) to be 
     * sent to the database. The scale argument may further qualify this type.
     * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types
     *          this is the number of digits after the decimal.  For all other
     *          types this value will be ignored,
	 * @exception SQLException thrown on failure.
     */
    public final void setObject(int parameterIndex, Object x, int targetSqlType, int scale)
            throws SQLException {

		if (x == null) {
			setNull(parameterIndex, targetSqlType);
			return;
		}

		int paramJDBCType = getParameterJDBCType(parameterIndex);

		if (paramJDBCType != java.sql.Types.JAVA_OBJECT) {
			if (!DataTypeDescriptor.isJDBCTypeEquivalent(paramJDBCType, targetSqlType)) {
				throw dataTypeConversion(parameterIndex, Util.typeName(targetSqlType));
			}
		}

		setObject(parameterIndex, x);
				
		/*
		* If the parameter type is DECIMAL or NUMERIC, then
		* we need to set the correct scale  or set it 
		* to the default which is zero for setObject.
		*/
		if ((paramJDBCType == Types.DECIMAL) || 
			 (paramJDBCType == Types.NUMERIC))
		{
			setScale(parameterIndex, scale);
		}	
	}

    /**
      * This method is like setObject above, but assumes a scale of zero.
	 * @exception SQLException thrown on failure.
      */
    public final void setObject(int parameterIndex, Object x, int targetSqlType)
		throws SQLException {
		setObject(parameterIndex, x, targetSqlType, 0);
	}

    /**
     * <p>Set the value of a parameter using an object; use the
     * java.lang equivalent objects for integral values.
     *
     * <p>The JDBC specification specifies a standard mapping from
     * Java Object types to SQL types.  The given argument java object
     * will be converted to the corresponding SQL type before being
     * sent to the database.
     *
     * <p>Note that this method may be used to pass datatabase
     * specific abstract data types, by using a Driver specific Java
     * type.
     *
     * @param parameterIndex The first parameter is 1, the second is 2, ...
     * @param x The object containing the input parameter value 
	 * @exception SQLException thrown on failure.
     */
    public final void setObject(int parameterIndex, Object x) throws SQLException {
		checkStatus();	


		int colType = getParameterJDBCType(parameterIndex);

		// JDBC Tutorial and Reference books states in the PreparedStatement
		// overview, that passing a untyped null into setObject() is not allowed.
		// JCC disallows this, basically SQL can not handle a untyped NULL.
		// Section 25.1.6 (Third edition), 24.1.5 (Second Edition)

		if (x == null) {
			//setNull(parameterIndex, colType);
			//return;
			throw dataTypeConversion(parameterIndex, "null");
		}
		
		if (colType == org.apache.derby.iapi.reference.JDBC20Translation.SQL_TYPES_JAVA_OBJECT) {
			try {
				/* JDBC is one-based, DBMS is zero-based */
				getParms().setParameterAsObject(parameterIndex - 1, x);
				return;

			} catch (Throwable t) {
				throw EmbedResultSet.noStateChangeException(t);
			}
		}


		// Need to do instanceof checks here so that the behaviour
		// for these calls is consistent with the matching setXXX() value.

		// These are the supported setObject conversions from JDBC 3.0 table B5

		if (x instanceof String) {
			setString(parameterIndex, (String) x);
			return;
		}

		if (x instanceof BigDecimal) {
			setBigDecimal(parameterIndex, (BigDecimal) x);
			return;
		}
		if (x instanceof Boolean) {
			setBoolean(parameterIndex, ((Boolean) x).booleanValue());
			return;
		}
		if (x instanceof Integer) {
			setInt(parameterIndex, ((Integer) x).intValue());
			return;
		}
		if (x instanceof Long) {
			setLong(parameterIndex, ((Long) x).longValue());
			return;
		}

		if (x instanceof Float) {
			setFloat(parameterIndex, ((Float) x).floatValue());
			return;
		}
		if (x instanceof Double) {
			setDouble(parameterIndex, ((Double) x).doubleValue());
			return;
		}

		if (x instanceof byte[]) {
			setBytes(parameterIndex, (byte[]) x);
			return;
		}

		if (x instanceof Date) {
			setDate(parameterIndex, (Date) x);
			return;
		}
		if (x instanceof Time) {
			setTime(parameterIndex, (Time) x);
			return;
		}
		if (x instanceof Timestamp) {
			setTimestamp(parameterIndex, (Timestamp) x);
			return;
		}

		if (x instanceof Blob) {
			setBlob(parameterIndex, (Blob) x);
			return;
		}
		if (x instanceof Clob) {
			setClob(parameterIndex, (Clob) x);
			return;
		}

		
		throw dataTypeConversion(parameterIndex, x.getClass().getName());

	}

    /**
     * @see java.sql.Statement#execute
	 * @exception SQLException thrown on failure.
     */
    public final boolean execute() throws SQLException {
		return executeStatement(activation, false, false);
	}
    /**
     * Set a parameter to a java.sql.Date value.  The driver converts this
     * to a SQL DATE value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
     * @exception SQLException Feature not implemented for now.
     */
    public final void setDate(int parameterIndex, java.sql.Date x, Calendar cal)
	    throws SQLException 
	{
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x, cal);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a java.sql.Time value.  The driver converts this
     * to a SQL TIME value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
     * @exception SQLException Feature not implemented for now.
     */
    public final void setTime(int parameterIndex, java.sql.Time x, Calendar cal)
	    throws SQLException 
	{
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x, cal);

		} catch (Throwable t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}

    /**
     * Set a parameter to a java.sql.Timestamp value.  The driver
     * converts this to a SQL TIMESTAMP value when it sends it to the
     * database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value 
     * @exception SQLException Feature not implemented for now.
     */
    public final void setTimestamp(int parameterIndex, java.sql.Timestamp x, Calendar cal)
	    throws SQLException 
	{
		checkStatus();
		try {
			/* JDBC is one-based, DBMS is zero-based */
			getParms().getParameterForSet(parameterIndex - 1).setValue(x, cal);

		} catch (StandardException t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}
	/**
	 * Get the ParameterValueSet from the activation
	 *
	 *
	 * @return	The ParameterValueSet for the activation
	 *
	 */
	public final ParameterValueSet getParms() {

		return activation.getParameterValueSet();
	}


	/**
		Check the parameterINdex is in range and return the
		array of type descriptors.

		@exception SQLException parameter is out of range
	*/
	protected final DataTypeDescriptor[] getTypes(int parameterIndex)
		throws SQLException {

		DataTypeDescriptor[] types = preparedStatement.getParameterTypes();

		if (types == null) {
			throw newSQLException(SQLState.NO_INPUT_PARAMETERS);
		}

		/* Check that the parameterIndex is in range. */
		if (parameterIndex < 1 ||
				parameterIndex > types.length) {

			/* This message matches the one used by the DBMS */
			throw newSQLException(SQLState.LANG_INVALID_PARAM_POSITION, 
            new Integer(parameterIndex), new Integer(types.length));
		}
		return types;
	}

	/**
		Get the target JDBC type for a parameter. Will throw exceptions
		if the parameter index is out of range

		@exception SQLException parameter is out of range
	*/
	protected int getParameterJDBCType(int parameterIndex)
		throws SQLException {

		DataTypeDescriptor[] types = getTypes(parameterIndex);

		int type = types[parameterIndex -1] == null ? 
			Types.OTHER :
			types[parameterIndex - 1].getTypeId().getJDBCTypeId();

		if (SanityManager.DEBUG) {
			//int pmType = getEmbedParameterSetMetaData().getParameterType(parameterIndex);
			//if (type != pmType) {
				//SanityManager.THROWASSERT("MISMATCH PARAMETER META DATA param " + parameterIndex + " types " + type + " != " + pmType + "\n" + SQLText);
			//}
		}

		return type;
	}

    /**
     * Set the scale of a parameter.
     *
     * @param parameterIndex The first parameter is 1, the second is 2, ...
     * @param scale	The scale
	 * @exception SQLException thrown on failure.
     */
    private void setScale(int parameterIndex, int scale)
		throws SQLException 
	{
		checkStatus();

		if (scale < 0)
			throw newSQLException(SQLState.BAD_SCALE_VALUE, new Integer(scale));
		
		try {

			ParameterValueSet pvs = getParms();

			/* JDBC is one-based, DBMS is zero-based */
			DataValueDescriptor value = pvs.getParameter(parameterIndex - 1);


			int origvaluelen = value.getLength();
			((VariableSizeDataValue)
						value).setWidth(VariableSizeDataValue.IGNORE_PRECISION, 
							scale, 
							false);

			if (value.getLength() < origvaluelen)
			{
				activation.addWarning(StandardException.newWarning(SQLState.LANG_VALUE_TRUNCATED, value.getString()));
			}

		} catch (StandardException t) {
			throw EmbedResultSet.noStateChangeException(t);
		}
	}


	/**
    * Immitate the function in JDBC 3.0
    *
    * Retrieves the number, types and properties of this PreparedStatement
    * object's parameters.
    *
    * @return a EmbedParameterSetMetaData object that contains information about the
    * number, types and properties of this PreparedStatement object's parameters.
    * @exception SQLException if a database access error occurs
	*/
	public EmbedParameterSetMetaData getEmbedParameterSetMetaData()
    	throws SQLException
	{
	  checkExecStatus();
	  return new EmbedParameterSetMetaData(
				getParms(), preparedStatement.getParameterTypes());

	}
	/**
    * JDBC 3.0
    *
    * Sets the designated parameter to the given java.net.URL value. The driver
    * converts this to an SQL DATALINK value when it sends it to the database.
    *
    * @param parameterIndex - the first parameter is 1, the second is 2, ...
    * @param x - the java.net.URL object to be set
    * @exception SQLException Feature not implemented for now.
	*/
	public final void setURL(int parameterIndex, java.net.URL x)
    throws SQLException
	{
		throw Util.notImplemented();
	}

	//
	// methods to be overridden in subimplementations
	// that want to stay within their subimplementation.
	//
	protected EmbedResultSetMetaData newEmbedResultSetMetaData(ResultDescription resultDesc) {

		return new EmbedResultSetMetaData(resultDesc.getColumnInfo());
	}

	public String toString() {

		if (activation != null)
			return activation.getPreparedStatement().getObjectName();
		return super.toString();
	}

	/*
	**
	*/
	public void transferParameters(EmbedPreparedStatement newStatement) throws SQLException {

		try {
			newStatement.activation.setParameters(getParms(), preparedStatement.getParameterTypes());
		} catch (StandardException se) {
			throw EmbedResultSet.noStateChangeException(se);
		}
	}

	protected boolean executeStatement(Activation a,
                     boolean executeQuery, boolean executeUpdate)
                     throws SQLException {

		checkExecStatus();
		checkIfInMiddleOfBatch();
		clearResultSets();
		return super.executeStatement(a, executeQuery, executeUpdate);
	}

	protected final SQLException dataTypeConversion(int column, String sourceType)
		throws SQLException {
		SQLException se = newSQLException(SQLState.LANG_DATA_TYPE_GET_MISMATCH, getEmbedParameterSetMetaData().getParameterTypeName(column),
			sourceType);
		return se;
	}
}
